package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.annotation.cache.Cache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.core.annotation.AnnotationUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author Haoran Sun
 * @since 2024/08/26
 */


@Component
@Aspect
public class GuiguCacheAspect {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.atguigu.tingshu.common.annotation.cache.Cache)")
    public void cachePointCut() {
    }

    @SneakyThrows
    @Around("cachePointCut()")
    public Object cacheAspect(ProceedingJoinPoint pjp) {

        Object obj = null;
        // 参数列表
        Object[] args = pjp.getArgs();

        // 获取注解参数
        MethodSignature signature = (MethodSignature) pjp.getSignature();

        Cache annotation = AnnotationUtils.getAnnotation(
                signature.getMethod(),
                Cache.class
        );

        String prefix = Objects.requireNonNull(annotation).prefix();


        // 组成缓存的key
        String key = prefix + Arrays.toString(args);
        if (args.length == 0){
            key = prefix;
        }

        String s = stringRedisTemplate.opsForValue().get(key);
        if (s == null){
            RLock lock = redissonClient.getLock(key + ":lock");
            boolean tryLock = lock.tryLock(
                    RedisConstant.CACHE_LOCK_EXPIRE_PX1,
                    RedisConstant.CACHE_LOCK_EXPIRE_PX2,
                    TimeUnit.SECONDS
            );
            if (tryLock){
                s = stringRedisTemplate.opsForValue().get(key);
                if (s != null){
                    return JSONObject.parseObject(s, signature.getReturnType());
                }
                try {
                    obj = pjp.proceed(args);
                    if (obj == null){
                        try {
                            obj = signature.getReturnType().getConstructor().newInstance();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        stringRedisTemplate.opsForValue().set(
                                key,
                                JSONObject.toJSONString(obj),
                                RedisConstant.CACHE_TEMPORARY_TIMEOUT,
                                TimeUnit.SECONDS);
                        return obj;
                    }
                    stringRedisTemplate.opsForValue().set(
                            key,
                            JSONObject.toJSONString(obj),
                            RedisConstant.CACHE_TIMEOUT,
                            TimeUnit.SECONDS
                    );
                    return obj;
                }finally {
                    lock.unlock();
                }
            }
        }

        return JSONObject.parseObject(s, signature.getReturnType());
    }




}
